package liquidfs.ui.vfs;

import java.io.*;
import java.nio.*;
import java.util.*;
import java.util.logging.*;

import net.decasdev.dokan.*;

import liquidfs.*;
import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.data.ex.*;
import liquidfs.data.ex.FileNotFoundException;
import liquidfs.data.fs.*;

public class WindowsVirtualFileSystemOperations implements DokanOperations {
	
	private Application app;
	private LiquidFileSystem fs;
	private HandleManager manager;
	private WindowsVirtualFileSystem vfs;
	private static Logger log = Logger.getLogger("liquidfs.ui.vfs");
	
	public WindowsVirtualFileSystemOperations (Application app, WindowsVirtualFileSystem vfs) {
		this.app = app;
		this.vfs = vfs;
		this.fs = app.getFileSystem();
		this.manager = app.getHandleManager();
	}
	
	@Override
	public long onCreateFile(String fileName, int desiredAccess, int shareMode, int creationDisposition, int flagsAndAttributes, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, desiredAccess, shareMode, creationDisposition, flagsAndAttributes));
	   
	   // Convert windows modes to our modes
	   SharingMode sharingMode;
	   OpenMode openMode;
	   
	   // Convert the share mode
	   if ( (shareMode & FileShareMode.FILE_SHARE_DELETE) == 1 || (shareMode & FileShareMode.FILE_SHARE_WRITE) == 1){
	      sharingMode = SharingMode.WRITE;
	   }
	   else if ( (shareMode & FileShareMode.FILE_SHARE_READ) == 1) {
         sharingMode = SharingMode.READ;
      }
	   else {
	      sharingMode = SharingMode.NONE;
	   }
	   
	   // Convert the open mode
	   if ( (desiredAccess & FileAccess.GENERIC_WRITE) == 1) {
	      openMode = OpenMode.READ_WRITE;
	   }
	   else if ( (desiredAccess & FileAccess.GENERIC_READ) == 1) {
         openMode = OpenMode.READ;
      }
	   else {
	      openMode = OpenMode.NONE;
	   }
	   
	   // Get the handle
	   Handle handle = manager.createHandle(openMode, sharingMode, fileName);
	   
	   // If we couldn't get the handle, then it must be a sharing violation
	   if (handle == null) {
	      throw new DokanOperationException(WinError.ERROR_SHARING_VIOLATION);
	   }
	   
	   fileInfo.handle = handle.getID();
	   fileInfo.isDirectory = handle.getObject().isDirectory();
	   
		return handle.getID();
	}

	@Override
	public long onOpenDirectory(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   
	   // Get the handle
	   Handle handle = manager.createHandle(OpenMode.NONE, SharingMode.WRITE, fileName);
	   
	   // If we couldn't get the handle, then it must be a sharing violation
      if (handle == null) {
         throw new DokanOperationException(WinError.ERROR_SHARING_VIOLATION);
      }
      
      fileInfo.handle = handle.getID();
      fileInfo.isDirectory = handle.getObject().isDirectory();
	   
		return handle.getID();
	}

	@Override
	public void onCreateDirectory(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   // TODO Auto-generated method stub
	}

	@Override
	public void onCleanup(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   
	   manager.destroyHandle(fileInfo.handle);
		
	}

	@Override
	public void onCloseFile(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   
	   
		
	}

	@Override
	public int onReadFile(String fileName, ByteBuffer buffer, long offset, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, "<buffer>", offset));

	   Handle handle = manager.getHandle(fileInfo.handle);
	   LiquidFile file;
	   
	   try {
	      file = handle.getObject().asFile();
	   }
	   catch (WrongObjectTypeException e) {
	      throw new DokanOperationException(WinError.ERROR_DIRECTORY);
	   }
	   
	   if (!file.isOpen())
	      file.open(handle.getOpenMode());
	   
	   InputStream stream = file.getInputStream(offset);
	   
	   try {
	      int read = stream.read(buffer.array(), 0, buffer.capacity());
	      return read;
	   }
	   catch (FileNotFoundException e) {
	      throw new DokanOperationException(WinError.ERROR_FILE_NOT_FOUND);
	   }
	   catch (IOException e) {
	      throw new DokanOperationException(WinError.ERROR_GEN_FAILURE);
	   }
	   
	}

	@Override
	public int onWriteFile(String fileName, ByteBuffer buffer, long offset,	DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, "<buffer>", offset));
	   // TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void onFlushFileBuffers(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   // TODO Auto-generated method stub
		
	}

	@Override
	public ByHandleFileInformation onGetFileInformation(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   
	   // Get an object that represents this path
      LiquidObject object = app.getFileSystem().getPath(fileName);
	   
	   ByHandleFileInformation info = new ByHandleFileInformation(
	      object.isDirectory() ? WindowsFileAttribute.DIRECTORY.getConstant() : 0, 
	      (object.getCreationDate().getTime()     * 10000000) + 116444736000000000L, 
	      (object.getLastAccessedDate().getTime() * 10000000) + 116444736000000000L, 
	      (object.getModificationDate().getTime() * 10000000) + 116444736000000000L, 
	      4474, /* Volume serial number */
	      object.getSize(), 
	      1, /* Links pointing to this file */
	      0  /* Unique identifier */
	   );
	   
	   return info;
	}

   @Override
	public Win32FindData[] onFindFiles(String pathName, DokanFileInfo fileInfo) throws DokanOperationException {

      log.fine(getMethodName().substring(2) + getParameters(pathName));
      
      // Get an object that represents this path
		LiquidObject object = app.getFileSystem().getPath(pathName);
		LiquidDirectory directory;
		
		// Get a directory out of it, if it is indeed a directory like it should be
		try {
		   directory = object.asDirectory();
		}
		catch (WrongObjectTypeException e) {
		   throw new DokanOperationException(WinError.ERROR_INVALID_HANDLE);
		}
		
		// Get the contents of this directory
		List<LiquidObject> objects;
		
		// Watch for any problems that could occur during this, like if we can't find this folder, or if something else goes wrong
		try {
		   objects = directory.getContents();
		}
		catch (FileNotFoundException e) {
		   log.log(Level.SEVERE, e.getLocalizedMessage());
		   throw new DokanOperationException(WinError.ERROR_FILE_NOT_FOUND);
		}
		catch (FileSystemException e) {
		   log.log(Level.SEVERE, e.getLocalizedMessage());
		   throw new DokanOperationException(WinError.ERROR_GEN_FAILURE);
		}
		catch (RuntimeException e) {
		   log.log(Level.SEVERE, "Unexpected error occured: " + e.getLocalizedMessage());
		   throw new DokanOperationException(WinError.ERROR_GEN_FAILURE);
		}

		// Create the Windows structure to represent the result
		Win32FindData[] array = new Win32FindData[objects.size()];
		
		// Fill it with data from our API
		for (int i = 0; i < objects.size(); ++i) {
		   
		   LiquidObject record = objects.get(i);
		   Win32FindData data = new Win32FindData();
		   
		   // Get basic data
		   data.fileName = record.getName();
		   data.alternateFileName = "";
		   data.fileSize = record.getSize();
		   
		   // Convert the timestamps
		   data.lastAccessTime = (record.getLastAccessedDate().getTime() * 10000000) + 116444736000000000L;
		   data.lastWriteTime  = (record.getModificationDate().getTime() * 10000000) + 116444736000000000L;
		   data.creationTime   = (record.getCreationDate().getTime()     * 10000000) + 116444736000000000L;
		   
		   // Set file attributes
		   data.fileAttributes = 0;
		   if (record.isDirectory())
		      data.fileAttributes |= WindowsFileAttribute.DIRECTORY.getConstant();
		   
		   array[i] = data;
		}
		
		// Return it back to the device driver
		return array;
	}

	@Override
	public Win32FindData[] onFindFilesWithPattern(String pathName, String searchPattern, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(pathName, searchPattern));
	   
	   // Get an object that represents this path
      LiquidObject object = app.getFileSystem().getPath(pathName);
      LiquidDirectory directory;
      
      // Get a directory out of it, if it is indeed a directory like it should be
      try {
         directory = object.asDirectory();
      }
      catch (WrongObjectTypeException e) {
         throw new DokanOperationException(WinError.ERROR_INVALID_HANDLE);
      }
      
      // Get the contents of this directory
      List<LiquidObject> objects;
      
      // Watch for any problems that could occur during this, like if we can't find this folder, or if something else goes wrong
      try {
         objects = directory.getContents();
      }
      catch (FileNotFoundException e) {
         log.log(Level.SEVERE, e.getLocalizedMessage());
         throw new DokanOperationException(WinError.ERROR_FILE_NOT_FOUND);
      }
      catch (FileSystemException e) {
         log.log(Level.SEVERE, e.getLocalizedMessage());
         throw new DokanOperationException(WinError.ERROR_GEN_FAILURE);
      }
      catch (RuntimeException e) {
         log.log(Level.SEVERE, "Unexpected error occured: " + e.getLocalizedMessage());
         throw new DokanOperationException(WinError.ERROR_GEN_FAILURE);
      }

      // Create the Windows structure to represent the result
      List<Win32FindData> list = new ArrayList<Win32FindData>(objects.size());
      
      // Fill it with data from our API
      for (int i = 0; i < objects.size(); ++i) {
         
         LiquidObject record = objects.get(i);
         
         if (!Dokan.isNameInExpression(searchPattern, record.getName(), true))
            continue;
         
         Win32FindData data = new Win32FindData();
         
         // Get basic data
         data.fileName = record.getName();
         data.alternateFileName = "";
         data.fileSize = record.getSize();
         
         // Convert the timestamps
         data.lastAccessTime = (record.getLastAccessedDate().getTime() * 10000000) + 116444736000000000L;
         data.lastWriteTime  = (record.getModificationDate().getTime() * 10000000) + 116444736000000000L;
         data.creationTime   = (record.getCreationDate().getTime()     * 10000000) + 116444736000000000L;
         
         // Set file attributes
         data.fileAttributes = 0;
         if (record.isDirectory())
            data.fileAttributes |= WindowsFileAttribute.DIRECTORY.getConstant();
         
         list.add(data);
      }
      
      // Return it back to the device driver
      return (Win32FindData[]) list.toArray();
	}

	@Override
	public void onSetFileAttributes(String fileName, int fileAttributes, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, fileAttributes));// TODO Auto-generated method stub
		
	}

	@Override
	public void onSetFileTime(String fileName, long creationTime, long lastAccessTime, long lastWriteTime, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, creationTime, lastAccessTime, lastWriteTime));
	   // TODO Auto-generated method stub
		
	}

	@Override
	public void onDeleteFile(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   // TODO Auto-generated method stub
		
	}

	@Override
	public void onDeleteDirectory(String fileName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName));
	   // TODO Auto-generated method stub
	}

	@Override
	public void onMoveFile(String existingFileName, String newFileName,	boolean replaceExisting, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(existingFileName, newFileName, replaceExisting));
	   // TODO Auto-generated method stub
		
	}

	@Override
	public void onSetEndOfFile(String fileName, long length, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, length));
	   // TODO Auto-generated method stub
		
	}

	@Override
	public void onLockFile(String fileName, long byteOffset, long length, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, byteOffset, length));
	   // TODO Auto-generated method stub
		
	}

	@Override
	public void onUnlockFile(String fileName, long byteOffset, long length,	DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(fileName, byteOffset, length));
	   // TODO Auto-generated method stub
	}

	@Override
	public DokanDiskFreeSpace onGetDiskFreeSpace(DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2));
	   
	   // Get the mount point as a File
	   File file = new File(this.app.getConfiguration().getBackendPath());
	   
	   // Get the root of this file
	   while (file.getParentFile() != null)
	      file = file.getParentFile();
	   
	   DokanDiskFreeSpace space = new DokanDiskFreeSpace();
	   space.freeBytesAvailable = file.getUsableSpace();
	   space.totalNumberOfFreeBytes = file.getFreeSpace();
	   space.totalNumberOfBytes = file.getTotalSpace();
	   
		return space;
	}

	@Override
	public DokanVolumeInformation onGetVolumeInformation(String volumeName, DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2) + getParameters(volumeName));
	   
	   DokanVolumeInformation info = new DokanVolumeInformation();
	   info.volumeName = app.getConfiguration().getVolumeName();
	   info.volumeSerialNumber = 4474;
	   info.maximumComponentLength = 255;
	   info.fileSystemName = "LiquidFS";
	   info.fileSystemFlags = 0;
	   
		return info;
	}

	@Override
	public void onUnmount(DokanFileInfo fileInfo) throws DokanOperationException {
	   log.fine(getMethodName().substring(2));
	   // TODO Auto-generated method stub
	}

	/**
    * Get the current method name. <br />
    * Utility function
    * @return method name
    */
	public static String getMethodName() {
	   return getMethodName(0);
	}
	
	/**
	 * Get the method name for a depth in call stack. <br />
	 * Utility function
	 * @param depth depth in the call stack (0 means current method, 1 means call method, ...)
	 * @return method name
	 */
	public static String getMethodName(final int depth)
	{
	  final StackTraceElement[] ste = Thread.currentThread().getStackTrace();

	  //System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
	  // return ste[ste.length - depth].getMethodName();  //Wrong, fails for depth = 0
	  return ste[ste.length - 1 - depth].getMethodName(); //Thank you Tom Tresansky
	}
	
	public static String getParameters (Object... args)
	{
	   String parameters = " ";
	   
	   for (Object s : args) {
	      parameters += s.toString() + ", ";
	   }
	   
	   parameters = parameters.substring(0, parameters.length() - 2);
	   
	   return parameters;
	}

}
